namespace NAct.Tests
{
	using System;
	using Attributes;
	using NUnit.Framework;
	using NUnit.Framework.ExtensionMethods;
	using NUnit.Framework.SyntaxHelpers;
	using Support;

	[ TestFixture ]
	public class ScenarioRunnerFixture
	{
		private ScenarioRunner runner;

		[ SetUp ]
		public void Set_up_test_context()
		{
			runner = new ScenarioRunner();
		}

		[ Test ]
		public void Should_be_able_to_load_lines_from_a_scenario_text_file()
		{
			runner.Load( "scenario.txt" );

			runner.Steps.Count.Should( Be.EqualTo( 3 ) );
			runner.Steps[ 0 ].Text.Should( Be.EqualTo( "Given the numbers 1,1" ) );
			runner.Steps[ 1 ].Text.Should( Be.EqualTo( "When the numbers are added together" ) );
			runner.Steps[ 2 ].Text.Should( Be.EqualTo( "Then the result should be 2" ) );
		}

		[ Test ]
		public void Should_be_able_to_pull_out_the_scenario_from_the_text_file()
		{
			runner.Load( "scenario.txt" );

			runner.Scenario.Should( Be.EqualTo( "Simple math" ) );
		}

		[ Test ]
		public void Should_be_able_to_get_steps_from_specified_type()
		{
			runner.Load( "scenario.txt" ).Parse<SimpleMathType>();

			runner.Steps[ 0 ].Method.Name.Should( Be.EqualTo( "CollectNumbers" ) );
			runner.Steps[ 1 ].Method.Name.Should( Be.EqualTo( "AddNumbers" ) );
			runner.Steps[ 2 ].Method.Name.Should( Be.EqualTo( "VerifyResult" ) );
		}

		[ Test ]
		public void Should_be_able_to_pull_parameter_info_from_methods()
		{
			runner.Load( "scenario.txt" ).Parse<SimpleMathType>();

			runner.Steps[ 0 ].Parameters.Count.Should( Be.EqualTo( 1 ) );
			runner.Steps[ 0 ].Parameters[ 0 ].Type.Should( Be.EqualTo( typeof( int[] ) ) );
			runner.Steps[ 0 ].Parameters[ 0 ].Name.Should( Be.EqualTo( "values" ) );
			runner.Steps[ 0 ].Parameters[ 0 ].Value.Should( Be.EqualTo( new[] { 1, 1 } ) );

			runner.Steps[ 1 ].Parameters.Count.Should( Be.EqualTo( 0 ) );

			runner.Steps[ 2 ].Parameters.Count.Should( Be.EqualTo( 1 ) );
			runner.Steps[ 2 ].Parameters[ 0 ].Type.Should( Be.EqualTo( typeof( int ) ) );
			runner.Steps[ 2 ].Parameters[ 0 ].Name.Should( Be.EqualTo( "result" ) );
			runner.Steps[ 2 ].Parameters[ 0 ].Value.Should( Be.EqualTo( 2 ) );
		}

		[ Test ]
		public void Should_be_able_to_run_the_scenario()
		{
			runner.Load( "scenario.txt" ).Parse<SimpleMathType>().Run();
		}

		[ Test ]
		public void Should_be_able_to_run_a_more_complex_scenario()
		{
			runner.Load( "person-scenario.txt" ).Parse<PersonScenario>().Run();
		}

		[ Test ]
		public void Should_be_able_to_run_a_test_with_floating_point_math()
		{
			runner.Load( "pi-calculation.txt" ).Parse<PiCalculation>().Run();
		}

		[ Test ]
		public void Should_have_NotRun_steps_before_they_are_run()
		{
			runner.Load( "scenario.txt" ).Parse<SimpleMathType>();
			foreach ( ScenarioStep step in runner.Steps )
			{
				Assert.That( step.Result, Is.EqualTo( Result.NotRun ) );
			}
		}

		[ Test ]
		public void Should_mark_a_step_as_ignored_if_it_does_not_have_a_method_assigned()
		{
			runner.Load( "scenario.txt" ).Parse<SimpleMathType>();

			runner.Steps[ 2 ].Method = null;
			runner.Steps[ 2 ].Result.Should( Be.EqualTo( Result.NotRun ) );

			runner.Run();
			runner.Steps[ 2 ].Result.Should( Be.EqualTo( Result.Ignored ) );
		}

		[ Test ]
		public void Should_mark_a_step_which_throws_as_failed()
		{
			runner.Load( "fail.txt" ).Parse<FailType>().Run();

			runner.Steps[ 0 ].Result.Should( Be.EqualTo( Result.Failed ) );
		}

		[ Test ]
		public void Should_attach_the_exception_to_the_step_it_occurred_in()
		{
			runner.Load( "fail.txt" ).Parse<FailType>().Run();

			runner.Steps[ 0 ].Exception.Should( Be.TypeOf( typeof( NotImplementedException ) ) );
		}

		[ Test ]
		public void Should_be_able_to_parse_a_character()
		{
			runner.Load( "parsers.txt" ).Parse<TypeParsing>();

			runner.Steps[ 0 ].Parameters[ 0 ].Value.Should( Be.EqualTo( 'a' ) );
		}

		[ Test ]
		public void Should_be_able_to_parse_a_single_value()
		{
			runner.Load( "parsers.txt" ).Parse<TypeParsing>();

			runner.Steps[ 1 ].Parameters[ 0 ].Value.Should( Be.EqualTo( 42.12 ).Within( 0.01 ) );
			Console.WriteLine( runner.Steps[ 1 ].Parameters[ 0 ].Value );
		}

		[ Test ]
		public void Should_be_able_to_parse_a_double_value()
		{
			runner.Load( "parsers.txt" ).Parse<TypeParsing>();

			runner.Steps[ 2 ].Parameters[ 0 ].Value.Should( Be.EqualTo( 3.141592 ).Within( 0.000001 ) );
			Console.WriteLine( runner.Steps[ 2 ].Parameters[ 0 ].Value );
		}

		[ Test ]
		public void Should_be_able_to_parse_a_decimal_value()
		{
			runner.Load( "parsers.txt" ).Parse<TypeParsing>();
			runner.Steps[ 3 ].Parameters[ 0 ].Value.Should( Be.EqualTo( 1.234 ) );
		}

		[ Test ]
		public void Should_be_able_to_parse_a_byte_value()
		{
			runner.Load( "parsers.txt" ).Parse<TypeParsing>();
			runner.Steps[ 4 ].Parameters[ 0 ].Value.Should( Be.EqualTo( 255 ) );
		}

		[ Test ]
		public void Should_be_able_to_parse_a_sbyte_value()
		{
			runner.Load( "parsers.txt" ).Parse<TypeParsing>();
			runner.Steps[ 5 ].Parameters[ 0 ].Value.Should( Be.EqualTo( 127 ) );
		}

		[ Test ]
		public void Should_be_able_to_parse_a_boolean_value()
		{
			runner.Load( "parsers.txt" ).Parse<TypeParsing>();
			runner.Steps[ 6 ].Parameters[ 0 ].Value.Should( Be.EqualTo( false ) );
		}

		[ Test ]
		public void Should_be_able_to_parse_a_boolean_array()
		{
			runner.Load( "parsers.txt" ).Parse<TypeParsing>();
			runner.Steps[ 7 ].Parameters[ 0 ].Value.Should( Be.EqualTo( new[] { true, false, true, false } ) );
		}

		[ Test ]
		public void Should_be_able_to_parse_a_url()
		{
			runner.Load( "parsers.txt" ).Parse<TypeParsing>();
			runner.Steps[ 8 ].Parameters[ 0 ].Value.Should( Be.EqualTo( new Uri( "http://www.google.com" ) ) );
		}

		[ Test, ExpectedException( typeof( NactException ), ExpectedMessage = "No parser for type Object" ) ]
		public void Should_throw_a_useful_exception_if_there_is_no_parser_available()
		{
			runner.Load( "parserExceptions.txt" ).Parse<ParserMissing>();
		}

		[ Test, ExpectedException( typeof( NactException ), ExpectedMessage =
			"Could not match parameter \"number\" to a match group - ensure the match groups have the same name as the parameters of the method they decorate"
			) ]
		public void Should_throw_a_useful_exception_if_the_match_group_does_not_match_a_parameter_name()
		{
			runner.Load( "matchException.txt" ).Parse<MatchFail>();
		}

		[ Test ]
		public void Should_report_passed_if_all_steps_passed()
		{
			runner.Load( "scenario.txt" ).Parse<SimpleMathType>().Run();

			runner.Passed.Should( Be.True );
		}

		[ Test ]
		public void Should_not_report_passed_if_any_step_did_not_pass()
		{
			runner.Load( "scenario.txt" ).Parse<SimpleMathType>();
			runner.Steps[ 1 ].Method = null;

			runner.Run();

			runner.Steps[ 1 ].Result.Should( Be.EqualTo( Result.Ignored ) );
			runner.Passed.Should( Be.False );
		}

		[ Test ]
		public void Should_be_able_to_add_a_parser()
		{
			runner.Parsers[ typeof( Object ) ] = input => (object) input;

			runner.Load( "parserExceptions.txt" ).Parse<ParserMissing>().Run();

			runner.Steps[ 0 ].Parameters[ 0 ].Value.Should( Be.EqualTo( "parser" ) );
			runner.Steps[ 0 ].Result.Should( Be.EqualTo( Result.Passed ) );
		}

		[ Test, ExpectedException( typeof( Exception ), ExpectedMessage = "Int parser replaced" ) ]
		public void Should_be_able_to_replace_a_parser()
		{
			runner.Parsers[ typeof( int ) ] = input =>
			                                  {
			                                  	throw new Exception( "Int parser replaced" );
			                                  };

			runner.Load( "scenario.txt" ).Parse<SimpleMathType>();
		}
	}

	public class MatchFail
	{
		[ Given( "a number (?<value>[0-9]+)" ) ]
		public void StepWithMismatchedGroupAndParameter( int number ) {}
	}

	public class ParserMissing
	{
		[ Given( "a step without a (?<thingy>[a-z]+)" ) ]
		public void StepWithNoParser( object thingy ) {}
	}
}